package com.opcode.nebulamobil.base.activities;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import com.opcode.nebulamobil.enums.VoiceRecordingSaveType;
import com.opcode.nebulamobil.information.ApplicationInfo;
import com.opcode.nebulamobil.information.requests.costarequests.PushTalkReq;
import com.opcode.nebulamobil.utility.JsonConverter;
import com.opcode.nebulamobil.utility.STSLog;
import com.opcode.nebulamobil.utility.UniqueIdCreator;

import android.content.res.AssetFileDescriptor;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.media.MediaPlayer.OnCompletionListener;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;


/**
 * Projelerimizde yer alacak olan ses ve görüntü işleri bu aktivitenin alt sınıfı olacak aktiviteler tarafından yönetilecektir.
 * @author Okan ERDOĞAN
 *
 * 8 Eyl 2014
 */
public abstract class MediaActivityBase extends FragmentActivityBase{
	
	/* 
	 * Ses dosyası oluşturmak için kullanılır.
	 */ 
	private AudioRecord recorder;
	
	/*
	 * Kayıt edicinin kaç bit üzerinden işlem yapacağını belirleyen değişkendir.
	 */
	private static final int RECORDER_BPP = 16;
	
	/*
	 * Kayıt edilecek dosya tipini belirler.
	 */
    private static final String AUDIO_RECORDER_FILE_EXT_WAV = ".mp3";
    
    /*
     * Kayıt edilecek klasör adını tutar.
     */
    private static final String AUDIO_RECORDER_FOLDER = "SesKayit";
    
    /*
     * Temp dosyasının adını tutar.
     */
    private static final String AUDIO_RECORDER_TEMP_FILE = "ses_kayit_temp.raw";
    
    /*
     * Ses örneği alma oranını tutar.
     */
    private static final int RECORDER_SAMPLERATE = 8000;
    
    /*
     * Kayıt kanalını belirler.
     */
    private static final int RECORDER_CHANNELS = AudioFormat.CHANNEL_IN_STEREO;
    
    /*
     * Şifre çözücü tipini belirler.
     */
    private static final int RECORDER_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
    
    /*
     * Ses kaydı yapılırken buffer edilecek dosyanın bir seferde ne kadar veri alacağını tutan değişkendir.
     */
    private int bufferSize = 0;
    
    /*
     * Kayıt işlemi ikinci bir thread aracılığıyla yapılır. Bu değişken ses kaydı kanalına ait değişkendir.
     */
    private Thread recordingThread = null;
    
    /*
     * Ses kaydının hali hazırda yapılıp yapılmadığını tutan bool tipindeki değişkendir.
     */
    private boolean isRecording = false;
    
    /*
     * Ses kaydı alınırken kullanılacak olan temp dosyasına ait dosya yolunu tutan değişkendir.
     */
    private String tempFileName,fileName;
    
    /*
     * Ses dosyalarını çalmak için kullanılacak olan player nesnesidir.
     */
    private MediaPlayer mediaPlayer;
    
    private boolean deleteAfterPlaying=false;
    
    private VoiceRecordingSaveType voiceRecordingSaveTypeRef;
    
    private long receiverPersonalId,senderPersonalId;
    
    
    /**
	 * Uygulamanın çalıştırılması esnasında yaşam döngüsünde ilk çalışacak yer burasıdır.
	 * Bu metod içerisinde uygulamanın dik olarak kullaılacağı belirtilir.
	 * Ayrıca fragment'ların yönetiminde kullanılacak olarak fragmentManager nesnesi elde edilir.
	 */
	protected void onCreate(Bundle savedInstanceState) {
		
		/*
		 * Minimum buffer oranı elde edilir.
		 */
		this.bufferSize = AudioRecord.getMinBufferSize(RECORDER_SAMPLERATE,RECORDER_CHANNELS,RECORDER_AUDIO_ENCODING);

		/*
		 * Ses dosyalarını çalmak için bir mediaplayer nesnesi oluşturuluyor. 
		 */
		this.mediaPlayer = new MediaPlayer();
		this.mediaPlayer.setOnCompletionListener(new OnCompletionListener() {

				/*
				 * Ses dosyasının çalması bittikten sonra bu metod çalışır.
				 */
		        @Override
		        public void onCompletion(MediaPlayer mp) {
		            mp.reset();
		            
		            /*
		             * Bu değer eğer true ise oynatılan ses dosyası oynatma bitince silinecektir.
		             * Aksi halde silinmeden ilgili klasörde kayıtlı kalacaktır.
		             */
		            if(deleteAfterPlaying){
		            	File file=new File(fileName);
			            if(file.exists()){
			            	file.delete();
			            }	
		            }          
		        }
		 });
		 
		super.onCreate(savedInstanceState);
	}

	
	
	
	/**
     * Bu metod çağrıldığı zaman ses kaydı alınmaya başlanır.
     * Alınan ses kaydı aynı zamanda verilen dosyaya yazılır.
     */
	public void startRecording(VoiceRecordingSaveType voiceRecordingSaveType,long receiverPersonalId,long senderPersonalId){
		this.tempFileName=getTempFilename();
		this.voiceRecordingSaveTypeRef=voiceRecordingSaveType;
		this.receiverPersonalId=receiverPersonalId;
		this.senderPersonalId=senderPersonalId;
		
		/* Kayıt işleminden sorumlu nesnedir.
		 * Bu nesne oluşturulurken, ses kalitesine ilişkin bazı değerler set edilir.
		 */
        recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, RECORDER_SAMPLERATE, RECORDER_CHANNELS,RECORDER_AUDIO_ENCODING, bufferSize);   
        recorder.startRecording();
        
        isRecording = true;
        
        /*
         * Kayıt ve dosyaya yazma işlemi farklı bir kanaldan yürütülüyor.
         */
        recordingThread = new Thread(new Runnable() {
                
                @Override
                public void run() {
                	
                	/*
                	 * Bu metod çağrılarak sunucuya ve dosyaya ses dosyasının yazılması sağlanıyor.
                	 */
                	writeAudioDataToTempFile(tempFileName,voiceRecordingSaveTypeRef);
                }
        },"AudioRecorder Thread");
        
        recordingThread.start();

	}
	
	
	/**
	 * Bu metod çağrıldığı zaman ses dosyası parametre olarak verilen temp dosyasına yazılır.
	 */
	private void writeAudioDataToTempFile(String tempFileName,VoiceRecordingSaveType voiceRecordingSaveType){
		
		//Byte dizisi şeklinde gönderilecek olan ses datası için bir byte dizi nesnesi oluşturuluyor.
		//Bunu veri havuzundan veri almak için kullanacağımız kaşık olarak düşünebilirsin.
		//Bir kaşıklamada bufferSize kadar veri alabilicez.
		//Ya da boşver kafan karışmasın kullan gitsin :)
        byte data[] = new byte[bufferSize];
        
        FileOutputStream os = null;
		
        try {
        	os = new FileOutputStream(tempFileName);
        } catch (FileNotFoundException e) {
        	e.printStackTrace();
        }
        
        int read = 0;
        
        if(null != os){
        	
        	//isRecording değeri true olduğu sürece kayıt işlemi devam edecektir.
        	while(isRecording){
        		read = recorder.read(data, 0, bufferSize);
                
        		if(voiceRecordingSaveType.equals(VoiceRecordingSaveType.RECORD_TO_FILE)){
        			if(AudioRecord.ERROR_INVALID_OPERATION != read){
            			
            			
            			//Burada elde edilen ses datasının dosyaya yazılma işlemini yapıyoruz.
            			try {
            				os.write(data);
            			} catch (IOException e) {
            				e.printStackTrace();
                        }                   
            		}
            	}else if(voiceRecordingSaveType.equals(VoiceRecordingSaveType.RECORD_TO_COSTA_SERVER)){

       			 	PushTalkReq pushTalkReq=new PushTalkReq();
       			 	pushTalkReq.setVoiceData(data);
       			 	pushTalkReq.setType("PushTalkReq");
       			 	pushTalkReq.setReceiverPersonalId(receiverPersonalId);
       			 	pushTalkReq.setSenderPersonalId(senderPersonalId);
       			 	pushTalkReq.setRequestId(UniqueIdCreator.createUniqueID());
       			 	pushTalkReq.setStopTalking(false);
                    
                    String jsonString=JsonConverter.toJsonString(pushTalkReq);
                    if(ApplicationInfo.getPushTalkDataListener()!=null){
                        ApplicationInfo.getPushTalkDataListener().pushMessageCame(jsonString);
                    }else{
                    	Log.e("MediaActivityBase", "PushTalkDataListener bulunamadı.");
                    }
            	}
        	}	
        	
        	
        	if(voiceRecordingSaveType.equals(VoiceRecordingSaveType.RECORD_TO_FILE)){
        		/*
            	 * Ses dosyasını yazma işlemi bitince outputstream nesnesini kapatılıyor.
            	 */
            	try {
            		os.close();
                } catch (IOException e) {
                	e.printStackTrace();
                }
        	}
        }
	}
	
	
	
	/**
	 * Kayıt işlemi sonucunda elde edilen temp dosyasından ses dosyası elde etmek için gerekli dosya adı bu metod ile elde edilir.
	 * @return
	 */
	private String getFilename(){
        String filepath = Environment.getExternalStorageDirectory().getPath();
        File file = new File(filepath,AUDIO_RECORDER_FOLDER);
        
        if(!file.exists()){
                file.mkdirs();
        } 
        return (file.getAbsolutePath() + "/" + System.currentTimeMillis() + AUDIO_RECORDER_FILE_EXT_WAV);
	}

	
	
	/**
	 * Temp dosyasına ait dosya yolunu geri döndürür.
	 * @return
	 */
	private String getTempFilename(){
        String filepath = Environment.getExternalStorageDirectory().getPath();
        File file = new File(filepath,AUDIO_RECORDER_FOLDER);
        
        if(!file.exists()){
                file.mkdirs();
        }
        
        File tempFile = new File(filepath,AUDIO_RECORDER_TEMP_FILE);
        
        if(tempFile.exists())
                tempFile.delete();
        
        return (file.getAbsolutePath() + "/" + AUDIO_RECORDER_TEMP_FILE);
	}
	
	
	
	/**
	 * Kaydı sonlandırmak için çağrılacak olan metoddur.
	 */
	public void stopRecording(VoiceRecordingSaveType voiceRecordingSaveType){
		if(null != recorder){
			isRecording = false;
                
			recorder.stop();
            recorder.release();
                
            recorder = null;
            recordingThread = null;
        }

		if(voiceRecordingSaveType.equals(VoiceRecordingSaveType.RECORD_TO_FILE)){
			this.fileName=getFilename();
			copyTempFileToMp3(this.tempFileName,fileName);
	        deleteTempFile();
		}else if(voiceRecordingSaveType.equals(VoiceRecordingSaveType.RECORD_TO_COSTA_SERVER)){
			 PushTalkReq pushTalkReq=new PushTalkReq();
			 pushTalkReq.setType("PushTalkReq");
			 pushTalkReq.setReceiverPersonalId(receiverPersonalId);
   			 pushTalkReq.setSenderPersonalId(senderPersonalId);
			 pushTalkReq.setRequestId(UniqueIdCreator.createUniqueID());
			 pushTalkReq.setStopTalking(true);
		     String jsonString=JsonConverter.toJsonString(pushTalkReq);
		        
		     if(ApplicationInfo.getPushTalkDataListener()!=null){
		    	 ApplicationInfo.getPushTalkDataListener().pushMessageCame(jsonString);
		     }else{
		    	 Log.e("MediaActivityBase", "PushTalkDataListener bulunamadı.");
		     }
		}
	}
	
	
	/**
	 * Temp dosyasında bulunan veriyi mp3 formatına çevirmek için kullanılan metoddur.
	 * @param inFilename
	 * @param outFilename
	 */
	private void copyTempFileToMp3(String inFilename,String outFilename){
        FileInputStream in = null;
        FileOutputStream out = null;
        long totalAudioLen = 0;
        long totalDataLen = totalAudioLen + 36;
        long longSampleRate = RECORDER_SAMPLERATE;
        int channels = 2;
        long byteRate = RECORDER_BPP * RECORDER_SAMPLERATE * channels/8;
        
        byte[] data = new byte[bufferSize];
        
        try {
        	in = new FileInputStream(inFilename);
            out = new FileOutputStream(outFilename);
            totalAudioLen = in.getChannel().size();
            totalDataLen = totalAudioLen + 36;
                
            Log.v("RecordActivity", "File size: " + totalDataLen);
                
                
            this.writeMp3FileHeader(out, totalAudioLen, totalDataLen,longSampleRate, channels, byteRate);
                
            while(in.read(data) != -1){
            	out.write(data);
            }
                
            in.close();
            out.close();
            
            audioFileReady(outFilename);
        } catch (FileNotFoundException e) {
                e.printStackTrace();
        } catch (IOException e) {
                e.printStackTrace();
        }
	}

	
	/**
	 * Mp3 formatında kayıt edilecek olan dosyanın header'ını oluşturur.
	 * @param out
	 * @param totalAudioLen
	 * @param totalDataLen
	 * @param longSampleRate
	 * @param channels
	 * @param byteRate
	 * @throws IOException
	 */
	private void writeMp3FileHeader(FileOutputStream out, long totalAudioLen,long totalDataLen, long longSampleRate, int channels,long byteRate) throws IOException {
        
        byte[] header = new byte[44];
        
        header[0] = 'R';  // RIFF/WAVE header
        header[1] = 'I';
        header[2] = 'F';
        header[3] = 'F';
        header[4] = (byte) (totalDataLen & 0xff);
        header[5] = (byte) ((totalDataLen >> 8) & 0xff);
        header[6] = (byte) ((totalDataLen >> 16) & 0xff);
        header[7] = (byte) ((totalDataLen >> 24) & 0xff);
        header[8] = 'W';
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        header[12] = 'f';  // 'fmt ' chunk
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';
        header[16] = 16;  // 4 bytes: size of 'fmt ' chunk
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        header[20] = 1;  // format = 1
        header[21] = 0;
        header[22] = (byte) channels;
        header[23] = 0;
        header[24] = (byte) (longSampleRate & 0xff);
        header[25] = (byte) ((longSampleRate >> 8) & 0xff);
        header[26] = (byte) ((longSampleRate >> 16) & 0xff);
        header[27] = (byte) ((longSampleRate >> 24) & 0xff);
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
        header[32] = (byte) (2 * 16 / 8);  // block align
        header[33] = 0;
        header[34] = RECORDER_BPP;  // bits per sample
        header[35] = 0;
        header[36] = 'd';
        header[37] = 'a';
        header[38] = 't';
        header[39] = 'a';
        header[40] = (byte) (totalAudioLen & 0xff);
        header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
        header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
        header[43] = (byte) ((totalAudioLen >> 24) & 0xff);

        out.write(header, 0, 44);
	}
	

	
	/**
	 * Bu metod çağrıldığı zaman temp dosyası silinir.
	 */
	private void deleteTempFile() {
        File file = new File(getTempFilename());  
        file.delete();
	}
	
	
	/**
	 * Verilen dosya yolunda ses dosyalarını oynatmak için kullanılır.
	 * Verilen ikinci parametreden sonra ses dosyasını oynattıktan sonra silinip silinmeyeceği belirlenmektedir..
	 * @param soundPath
	 * @param deleteAfterPlaying
	 */
	public void playSound(String soundPath,boolean deleteAfterPlaying){
		this.deleteAfterPlaying=deleteAfterPlaying;

	    try {
	    	if(!this.mediaPlayer.isPlaying()){
	    		this.mediaPlayer.setDataSource(soundPath);            
	    		this.mediaPlayer.prepare();
	    		this.mediaPlayer.setVolume(100f, 100f);
	    		this.mediaPlayer.setLooping(false);
	    		this.mediaPlayer.start();
	    	}
	    } catch (Exception e) {
	    	e.printStackTrace();
	    }
	 }
	
	
	/**
	 * Asset klasörü içerisinde yer alan ses dosyalarını çalmak için kullanılan metoddur.
	 * @param soundPath
	 */
	public void playSoundFromAsset(String soundPath) {
		MediaPlayer m = new MediaPlayer();

		m.setOnCompletionListener(new OnCompletionListener() {

			@Override
			public void onCompletion(MediaPlayer mp) {
				mp.release();
			}

		});

		try {

			AssetFileDescriptor descriptor = getAssets().openFd(soundPath);
			m.setDataSource(descriptor.getFileDescriptor(),descriptor.getStartOffset(), descriptor.getLength());

			descriptor.close();

			m.prepare();
			m.setVolume(100f, 100f);
			m.setLooping(false);
			m.start();

		} catch (Exception e) {
			STSLog.writeLog(e, this.getClass().getCanonicalName(),"Ses dosyası oynatılırken hata alındı.");
		}
	}
	
	public abstract void audioFileReady(String fileName);
    
}
